#include "netclient.h"

#include "../model/datacenter.h"

namespace network {

NetClient::NetClient(model::DataCenter* dataCenter)
    :dataCenter(dataCenter)
{
    initWebsocket();
}

/////////////////////////////////////////////
/// utility function
/////////////////////////////////////////////

QString NetClient::makeRequestId()
{
    return "R" + QUuid::createUuid().toString().sliced(25, 12);
}

/////////////////////////////////////////////
/// HTTP
/////////////////////////////////////////////

void NetClient::ping()
{
    QNetworkRequest req;
    req.setUrl(HTTP_URL + "/ping");

    QNetworkReply* rsp = httpClient.get(req);
    connect(rsp, &QNetworkReply::finished, this, [=]() {
        if (rsp->error() != QNetworkReply::NoError) {
            LOG() << rsp->errorString();
        } else {
            LOG() << rsp->readAll();
        }
        rsp->deleteLater();
    });
}

void NetClient::getMyself(const QString &loginSessionId)
{
    // construct HTTP body
    bite_im::GetUserInfoReq req;
    req.setRequestId(makeRequestId());
    req.setSessionId(loginSessionId);
    QByteArray body = req.serialize(&serializer);

    // construct HTTP request and send
    QNetworkReply* httpRsp = this->sendHttpRequest("/service/user/get_user_info", body);
    LOG() << "[http client] send getMyself request: " << req.requestId();

    connect(httpRsp, &QNetworkReply::finished, this, [=](){
        // handle HTTP response and get result
        bool ok;
        QString errString;
        auto rsp = this->handleHttpResponse<bite_im::GetUserInfoRsp>(httpRsp, &ok, &errString);

        if(!ok) {
            LOG() << "handle HTTP response failed, " << errString;
            return;
        }

        dataCenter->resetMyself(rsp);

        // send getMyselfDone signal
        emit dataCenter->getMyselfDone();
    });
}

void NetClient::getFriendList(const QString &loginSessionId)
{
    bite_im::GetFriendListReq req;
    req.setRequestId(makeRequestId());
    req.setSessionId(loginSessionId);
    QByteArray body = req.serialize(&serializer);

    QNetworkReply* reply = this->sendHttpRequest("/service/friend/get_friend_list", body);
    LOG() << "[http client] send getFriendList request: " << req.requestId();

    connect(reply, &QNetworkReply::finished, this, [=](){
        bool ok;
        QString errString;
        auto rsp = this->handleHttpResponse<bite_im::GetFriendListRsp>(reply, &ok, &errString);

        if(!ok) {
            LOG() << "handle HTTP response failed, " << errString;
            return;
        }

        dataCenter->resetFriendList(rsp);

        emit dataCenter->getFriendListDone();
    });
}

void NetClient::getChatSessionList(const QString &loginSessionId)
{
    bite_im::GetChatSessionListReq req;
    req.setRequestId(makeRequestId());
    req.setSessionId(loginSessionId);

    QByteArray body = req.serialize(&serializer);

    QNetworkReply* reply = this->sendHttpRequest("/service/friend/get_chat_session_list", body);
    LOG() << "[http client] send getChatSessionList request: " << req.requestId();

    connect(reply, &QNetworkReply::finished, this, [=](){
        bool ok;
        QString errString;
        auto rsp = this->handleHttpResponse<bite_im::GetChatSessionListRsp>(reply, &ok, &errString);

        if(!ok) {
            LOG() << "handle HTTP response failed, " << errString;
            return;
        }

        dataCenter->resetChatSessionList(rsp);

        emit dataCenter->getChatSessionListDone();
    });
}

void NetClient::getApplyList(const QString &loginSessionId)
{
    bite_im::GetPendingFriendEventListReq req;
    req.setRequestId(makeRequestId());
    req.setSessionId(loginSessionId);

    QByteArray body = req.serialize(&serializer);

    QNetworkReply* reply = this->sendHttpRequest("/service/friend/get_pending_friend_events", body);
    LOG() << "[http client] send getApplyList request: " << req.requestId();

    connect(reply, &QNetworkReply::finished, this, [=](){
        bool ok;
        QString errString;
        auto rsp = this->handleHttpResponse<bite_im::GetPendingFriendEventListRsp>(reply, &ok, &errString);

        if(!ok) {
            LOG() << "handle HTTP response failed, " << errString;
            return;
        }

        dataCenter->resetApplyList(rsp);

        emit dataCenter->getApplyListDone();
    });
}

void NetClient::getRecentMessageList(const QString &loginSessionId, const QString &chatSessionId, bool updateUI)
{
    bite_im::GetRecentMsgReq req;
    req.setRequestId(makeRequestId());
    req.setChatSessionId(chatSessionId);
    req.setMsgCount(50);

    QByteArray body = req.serialize(&serializer);

    QNetworkReply* reply = this->sendHttpRequest("/service/message_storage/get_recent", body);
    LOG() << "[http client] send getRecenMsgList request: " << req.requestId();
    connect(reply, &QNetworkReply::finished, this, [=](){
        bool ok;
        QString errString;
        auto rsp = this->handleHttpResponse<bite_im::GetRecentMsgRsp>(reply, &ok, &errString);

        if(!ok) {
            LOG() << "handle HTTP response failed, " << errString;
            return;
        }

        dataCenter->resetRecentMessageList(rsp, chatSessionId);

        if(updateUI) {
            emit dataCenter->getRecentMessageListDone(chatSessionId);
        } else {
            emit dataCenter->getRecentMessageListDoneNoUI(chatSessionId);
        }
    });
}

QNetworkReply *NetClient::sendHttpRequest(const QString &path, const QByteArray &body)
{
    QNetworkRequest httpReq;
    httpReq.setUrl(QUrl(HTTP_URL + path));
    httpReq.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-protobuf");
    QNetworkReply* httpRsp = httpClient.post(httpReq, body);
    return httpRsp;
}

void NetClient::sendMessage(const QString &loginSessionId, const QString &chatSessionId,
                            model::MessageType type, const QByteArray &content,
                            const QString& extraInfo)
{
    // 1.construct requset
    bite_im::NewMessageReq req;
    req.setRequestId(makeRequestId());
    req.setSessionId(loginSessionId);
    req.setChatSessionId(chatSessionId);

    bite_im::MessageContent msgContent;
    if(type == model::MessageType::TEXT_TYPE) {
        msgContent.setMessageType(bite_im::MessageTypeGadget::MessageType::STRING);

        bite_im::StringMessageInfo info;
        info.setContent(content);

        msgContent.setStringMessage(info);
    } else if(type == model::MessageType::FILE_TYPE) {
        msgContent.setMessageType(bite_im::MessageTypeGadget::MessageType::FILE);

        bite_im::FileMessageInfo info;
        info.setFileId(""); // fileid is a field generated by the server
        info.setFileSize(content.size());
        info.setFileName(extraInfo);
        info.setFileContents(content);

        msgContent.setFileMessage(info);
    } else if(type == model::MessageType::IMAGE_TYPE) {
        msgContent.setMessageType(bite_im::MessageTypeGadget::MessageType::IMAGE);

        bite_im::ImageMessageInfo info;
        info.setFileId(""); // fileid is a field generated by the server
        info.setImageContent(content);

        msgContent.setImageMessage(info);
    } else if(type == model::MessageType::SPEECH_TYPE) {
        msgContent.setMessageType(bite_im::MessageTypeGadget::MessageType::SPEECH);

        bite_im::SpeechMessageInfo info;
        info.setFileId(""); // fileid is a field generated by the server
        info.setFileContents(content);

        msgContent.setSpeechMessage(info);
    } else {
        LOG() << "unknown message type";
        return;
    }

    req.setMessage(msgContent);

    // 2.serialize
    QByteArray body = req.serialize(&serializer);
    // 3.send requset
    QNetworkReply* reply = this->sendHttpRequest("/service/message_transmit/new_message", body);
    LOG() << "[http client] send sendMessage request: " << req.requestId()
          << " loginSessionId: " << req.sessionId() << " chatSessionId: " << req.chatSessionId()
          << " msg: " << req.message().stringMessage().content();

    connect(reply, &QNetworkReply::finished, this, [=](){
        bool ok;
        QString errString;
        auto rsp = this->handleHttpResponse<bite_im::NewMessageRsp>(reply, &ok, &errString);

        if(!ok) {
            LOG() << "handle HTTP response failed, " << errString;
            return;
        }

        emit dataCenter->sendMessageDone(type, content, extraInfo);
    });
}

void NetClient::modifyNickname(const QString &loginSessionId, const QString &newName)
{
    bite_im::SetUserNicknameReq req;
    req.setRequestId(makeRequestId());
    req.setSessionId(loginSessionId);
    req.setNickname(newName);

    QByteArray body = req.serialize(&serializer);

    QNetworkReply* reply = this->sendHttpRequest("/service/user/set_nickname", body);
    LOG() << "[http client] send modifyNickname request: " << req.requestId();

    connect(reply, &QNetworkReply::finished, this, [=](){
        bool ok;
        QString errString;
        auto rsp = this->handleHttpResponse<bite_im::SetUserNicknameRsp>(reply, &ok, &errString);

        if(!ok) {
            LOG() << "handle HTTP response failed, " << errString;
            return;
        }

        dataCenter->resetNickname(newName);

        emit dataCenter->modifyNicknameDone();
    });
}

void NetClient::modifyDescription(const QString &loginSessionId, const QString &newDesc)
{
    bite_im::SetUserDescriptionReq req;
    req.setRequestId(makeRequestId());
    req.setSessionId(loginSessionId);
    req.setDescription(newDesc);

    QByteArray body = req.serialize(&serializer);

    QNetworkReply* reply = this->sendHttpRequest("/service/user/set_description", body);
    LOG() << "[http client] send modifyDescription request: " << req.requestId();

    connect(reply, &QNetworkReply::finished, this, [=](){
        bool ok;
        QString errString;
        auto rsp = this->handleHttpResponse<bite_im::SetUserDescriptionRsp>(reply, &ok, &errString);

        if(!ok) {
            LOG() << "handle HTTP response failed, " << errString;
            return;
        }

        dataCenter->resetDescription(newDesc);

        emit dataCenter->modifyDescriptionDone();
    });
}

void NetClient::getVerifyCode(const QString &newPhone)
{
    bite_im::PhoneVerifyCodeReq req;
    req.setRequestId(makeRequestId());
    req.setPhoneNumber(newPhone);

    QByteArray body = req.serialize(&serializer);

    QNetworkReply* reply = this->sendHttpRequest("/service/user/get_phone_verify_code", body);
    LOG() << "[http client] send getVerifyCode request: " << req.requestId();

    connect(reply, &QNetworkReply::finished, this, [=](){
        bool ok;
        QString errString;
        auto rsp = this->handleHttpResponse<bite_im::PhoneVerifyCodeRsp>(reply, &ok, &errString);

        if(!ok) {
            LOG() << "handle HTTP response failed, " << errString;
            return;
        }

        dataCenter->resetVerifyCodeId(rsp->verifyCodeId());

        emit dataCenter->getVerifyCodeDone();
    });
}

void NetClient::modifyPhone(const QString &loginSessionId, const QString &newPhone, const QString &verifyCodeId, const QString &verifyCode)
{
    bite_im::SetUserPhoneNumberReq req;
    req.setRequestId(makeRequestId());
    req.setSessionId(loginSessionId);
    req.setPhoneNumber(newPhone);
    req.setPhoneVerifyCodeId(verifyCodeId);

    QByteArray body = req.serialize(&serializer);

    QNetworkReply* reply = this->sendHttpRequest("/service/user/set_phone", body);
    LOG() << "[http client] send modifyPhone request: " << req.requestId();

    connect(reply, &QNetworkReply::finished, this, [=](){
        bool ok;
        QString errString;
        auto rsp = this->handleHttpResponse<bite_im::SetUserPhoneNumberRsp>(reply, &ok, &errString);

        if(!ok) {
            LOG() << "handle HTTP response failed, " << errString;
            return;
        }

        dataCenter->resetPhone(newPhone);

        emit dataCenter->modifyPhoneDone();
    });
}

void NetClient::modifyAvatar(const QString &loginSessionId, const QByteArray &image)
{
    bite_im::SetUserAvatarReq req;
    req.setRequestId(makeRequestId());
    req.setSessionId(loginSessionId);
    req.setAvatar(image);

    QByteArray body = req.serialize(&serializer);

    QNetworkReply* reply = this->sendHttpRequest("/service/user/set_avatar", body);
    LOG() << "[http client] send modifyAvatar request: " << req.requestId();

    connect(reply, &QNetworkReply::finished, this, [=](){
        bool ok;
        QString errString;
        auto rsp = this->handleHttpResponse<bite_im::SetUserAvatarRsp>(reply, &ok, &errString);

        if(!ok) {
            LOG() << "handle HTTP response failed, " << errString;
            return;
        }

        dataCenter->resetAvatar(image);

        emit dataCenter->modifyAvatarDone();
    });
}

void NetClient::deleteFriend(const QString &loginSessionId, const QString &userId)
{
    bite_im::FriendRemoveReq req;
    req.setRequestId(makeRequestId());
    req.setSessionId(loginSessionId);
    req.setPeerId(userId);

    QByteArray body = req.serialize(&serializer);

    QNetworkReply* reply = this->sendHttpRequest("/service/friend/remove_friend", body);
    LOG() << "[http client] send deleteFriend request: " << req.requestId();

    connect(reply, &QNetworkReply::finished, this, [=](){
        bool ok;
        QString errString;
        auto rsp = this->handleHttpResponse<bite_im::FriendRemoveRsp>(reply, &ok, &errString);

        if(!ok) {
            LOG() << "handle HTTP response failed, " << errString;
            return;
        }

        dataCenter->deleteFriend(userId);

        emit dataCenter->deleteFriendDone();
    });
}

void NetClient::sendFriendApply(const QString &loginSessionId, const QString &userId)
{
    bite_im::FriendAddReq req;
    req.setRequestId(makeRequestId());
    req.setSessionId(loginSessionId);
    req.setRespondentId(userId);

    QByteArray body = req.serialize(&serializer);

    QNetworkReply* reply = this->sendHttpRequest("/service/friend/add_friend_apply", body);
    LOG() << "[http client] send addFriendApply request: " << req.requestId();
    connect(reply, &QNetworkReply::finished, this, [=](){
        bool ok;
        QString errString;

        auto rsp = this->handleHttpResponse<bite_im::FriendAddRsp>(reply, &ok, &errString);

        if(!ok) {
            LOG() << "handle HTTP response failed, " << errString;
            return;
        }

        emit dataCenter->sendFriendApplyDone();
    });
}

void NetClient::acceptFriendApply(const QString &loginSessionId, const QString &userId)
{
    bite_im::FriendAddProcessReq req;
    req.setRequestId(makeRequestId());
    req.setAgree(true);
    req.setSessionId(loginSessionId);
    req.setUserId(userId);

    QByteArray body = req.serialize(&serializer);

    QNetworkReply* reply = this->sendHttpRequest("/service/friend/add_friend_process", body);
    LOG() << "[http client] send acceptFriendApply request: " << req.requestId();

    connect(reply, &QNetworkReply::finished, this, [=](){
        bool ok;
        QString errString;

        auto rsp = this->handleHttpResponse<bite_im::FriendAddProcessRsp>(reply, &ok, &errString);

        if(!ok) {
            LOG() << "handle HTTP response failed, " << errString;
            return;
        }

        // remove friend apply from Apply List && add friend to Friend List
        model::UserInfo userInfo = dataCenter->removeFromApplyList(userId);
        QList<model::UserInfo>* list = dataCenter->getFriendList();
        list->push_front(userInfo);

        emit dataCenter->acceptFriendApplyDone();
    });
}

void NetClient::rejectFriendApply(const QString &loginSessionId, const QString &userId)
{
    bite_im::FriendAddProcessReq req;
    req.setRequestId(makeRequestId());
    req.setAgree(false);
    req.setSessionId(loginSessionId);
    req.setUserId(userId);

    QByteArray body = req.serialize(&serializer);

    QNetworkReply* reply = this->sendHttpRequest("/service/friend/add_friend_process", body);
    LOG() << "[http client] send rejectFriendApply request: " << req.requestId();

    connect(reply, &QNetworkReply::finished, this, [=](){
        bool ok;
        QString errString;

        auto rsp = this->handleHttpResponse<bite_im::FriendAddProcessRsp>(reply, &ok, &errString);

        if(!ok) {
            LOG() << "handle HTTP response failed, " << errString;
            return;
        }

        // remove friend apply from Apply List
        dataCenter->removeFromApplyList(userId);

        emit dataCenter->rejectFriendApplyDone();
    });
}

void NetClient::createGroupChatSession(const QString &loginSessionId, const QList<QString> &userIdList)
{
    bite_im::ChatSessionCreateReq req;
    req.setRequestId(makeRequestId());
    req.setSessionId(loginSessionId);
    req.setChatSessionName("new group chat");
    req.setMemberIdList(userIdList);

    QByteArray body = req.serialize(&serializer);

    QNetworkReply* reply = this->sendHttpRequest("/service/friend/create_chat_session", body);
    LOG() << "[http client] send createGroupChatSession request: " << req.requestId();

    connect(reply, &QNetworkReply::finished, this, [=](){
        bool ok;
        QString errString;
        auto rsp = this->handleHttpResponse<bite_im::ChatSessionCreateRsp>(reply, &ok, &errString);

        if(!ok) {
            LOG() << "handle HTTP response failed, " << errString;
            return;
        }

        emit dataCenter->createGroupChatSessionDone();
    });
}

/////////////////////////////////////////////
/// Websocket
/////////////////////////////////////////////

void NetClient::initWebsocket()
{
    // 1. connect signal and slot
    connect(&websocketClient, &QWebSocket::connected, this, [=](){
        LOG() << "[Websocket client] connection established successfully";
        // this->sendAuth();
    });

    connect(&websocketClient, &QWebSocket::disconnected, this, [](){
        LOG() << "[Websocket client] connection disconnect";
    });

    connect(&websocketClient, &QWebSocket::errorOccurred, this, [=](const QAbstractSocket::SocketError& error){
        LOG() << "[Websocket client] connection error: " << error;
    });

    connect(&websocketClient, &QWebSocket::textMessageReceived, this, [=](const QString& msg){
        LOG() << "[Websocket client] receive text msg: " << msg;
    });

    connect(&websocketClient, &QWebSocket::binaryMessageReceived, this, [=](const QByteArray& msg){
        LOG() << "[Websocket client] receive binary msg";
        bite_im::NotifyMessage notifyMsg;
        notifyMsg.deserialize(&serializer, msg);

        this->handleNotifyMessage(notifyMsg);
    });

    // 2. establish connection with server
    websocketClient.open(WEBSOCKET_URL);
}

void NetClient::sendAuth()
{
    bite_im::ClientAuthenticationReq req;
    req.setRequestId(this->makeRequestId());
    req.setSessionId(this->dataCenter->getLoginSessionId());
    QByteArray data = req.serialize(&serializer);
    websocketClient.sendBinaryMessage(data);
    LOG() << req.requestId() << " " << req.sessionId();
}

void NetClient::handleNotifyMessage(const bite_im::NotifyMessage &notifyMsg)
{
    bite_im::NotifyTypeGadget::NotifyType type = notifyMsg.notifyType();
    if(type == bite_im::NotifyTypeGadget::NotifyType::CHAT_MESSAGE_NOTIFY) {
        model::Message msg;
        msg.load(notifyMsg.newMessageInfo().messageInfo());
        this->handleMessage(msg);
    } else if(type == bite_im::NotifyTypeGadget::NotifyType::CHAT_SESSION_CREATE_NOTIFY) {
        model::ChatSessionInfo newSession;
        newSession.load(notifyMsg.newChatSessionInfo().chatSessionInfo());
        this->hanleNewChatSession(newSession);
    } else if(type == bite_im::NotifyTypeGadget::NotifyType::FRIEND_ADD_APPLY_NOTIFY) {
        model::UserInfo applyInfo;
        applyInfo.load(notifyMsg.friendAddApply().userInfo());
        this->handleFriendApply(applyInfo);
    } else if(type == bite_im::NotifyTypeGadget::NotifyType::FRIEND_ADD_PROCESS_NOTIFY) {
        bool agree = notifyMsg.friendProcessResult().agree();
        model::UserInfo userInfo;
        userInfo.load(notifyMsg.friendProcessResult().userInfo());
        this->handleAcceptOrRejectFriendApply(userInfo, agree);
    } else if(type == bite_im::NotifyTypeGadget::NotifyType::FRIEND_REMOVE_NOTIFY) {
        QString userId = notifyMsg.friendRemove().userId();
        this->handleRemoveFriend(userId);
    } else {
        LOG() << "unknow notify msg type";
        return;
    }
}

void NetClient::handleMessage(const model::Message &msg)
{
    QList<model::Message>* list = dataCenter->getRecentMessageList(msg.sessionId);
    if(list == nullptr) {
        // the message list for this message's session isn't loaded yet
        connect(dataCenter, &model::DataCenter::getRecentMessageListDoneNoUI, this, &NetClient::receiveMessage, Qt::UniqueConnection);
        dataCenter->getRecentMessageListAsync(msg.sessionId, false);
    } else {
        list->push_back(msg);
        this->receiveMessage(msg.sessionId);
    }
}

void NetClient::receiveMessage(const QString& chatSessionId)
{
    if(chatSessionId == dataCenter->getCurrentSessionId()) {
        const model::Message lastMsg = dataCenter->getRecentMessageList(chatSessionId)->back();
        emit dataCenter->receiveMessageDone(lastMsg);
    } else {
        // set unread msg count
        dataCenter->addUnreadMsgCount(chatSessionId);
        int count = dataCenter->getUnreadMsgCount(chatSessionId);
    }

    // update sesion last message
    emit dataCenter->addSelfMessageDone(chatSessionId);
}

void NetClient::handleRemoveFriend(const QString &userId)
{
    dataCenter->deleteFriend(userId);
    emit dataCenter->deleteFriendDone();
}

void NetClient::handleFriendApply(const model::UserInfo &userInfo)
{
    dataCenter->addFriendApply(userInfo);
    emit dataCenter->addFriendApplyDone();
}

void NetClient::handleAcceptOrRejectFriendApply(const model::UserInfo &userInfo, bool agree)
{
    if(agree) {
        Toast::makeToast(userInfo.nickname + " 同意你的好友申请");
        QList<model::UserInfo>* list = dataCenter->getFriendList();
        list->push_front(userInfo);
        emit dataCenter->agreeFriendApplyDone();
    } else {
        Toast::makeToast(userInfo.nickname + " 拒绝你的好友申请");
    }
}

void NetClient::hanleNewChatSession(const model::ChatSessionInfo &newSession)
{
    QList<model::ChatSessionInfo>* list = dataCenter->getChatSessionList();
    if(list == nullptr) {
        return;
    }
    list->push_front(newSession);
    emit dataCenter->newChatSessionDone();
}

} // end network
